#include <stdio.h>

/*
 * factorial (阶乘)
 *
 * 类似文章:
 * tecknic\book-sotfware\ASM\论程序底层优化的一些方法与技巧.pdf
 */


/* 禁止GCC内联 */
__attribute__((noinline))
int calc(int n)
{
    int fact = 1, num = n + 1;
    int i;

    for (i = 0; i < num; i++) {
        fact *= i;
    }

    return fact;
}

int main()
{
    return calc(1000000000);
}

#if 0
/*
 * 性能最差，主要有三点原因：
 */
    1. 热点路径无用指令太多。
    2. 热点路径跳转指令太多。
    3. 热点路径内存访问太多。

    不加优化选项和加了-O3编译，对比反汇编代码区别

    加了-O3之后，GCC做的最大的优化是把所有变量存放在寄存器中，消除了所有的内存访问操作！

    优化之前的汇编代码，整个函数一共有10个内存访问操作，其中6个是在循环体内，而加了-O3之后，
    整个函数没有任何内存访问操作！难怪-O3编译后性能提升那么多！由此可见，内存访问相对寄存器访问的开销实在是太大了！
    当然，即便不使用-O3，也有优化内存操作的办法，

/*
 * 流水线
 */
    是把指令的执行过程分成多个阶段，每个阶段使用CPU内部不同的硬件资源来完成。
    以经典的5级流水线为例，一条指令的执行被分为5个阶段：

    • 取指(IF)：从内存中取出一条指令。

    • 译码(ID)：对指令进行解码，确定该指令要执行的操作。

    • 执行(EX)：执行该指令要执行的操作。

    • 访存(MEM)：进行内存访问操作。

    • 写回(WB)：把执行的结果写回寄存器或内存。

在时钟信号的驱动下，CPU依次来执行这些步骤，这就构成了指令流水线(pipeline)。

在CPU内部，执行每个阶段使用不同的硬件资源，从而可以让多条指令的执行时间相互重叠。

当第一条指令完成取指，进入译码阶段时，第二条指令就可以进入取指阶段了。

以此类推，在一个5级流水线中，理想情况下，可以有5条不同的指令的不同阶段在同时执行，

因此每个时钟周期都会有一条指令完成执行，从而大大提高了CPU执行指令的吞吐率，从而提高CPU整体性能。

这就叫做ILP - 指令级并行（Instruction Level Parallelism）。


通过把指令执行分为多个阶段，CPU每个时钟周期只处理一个阶段的工作，这样大大简化了CPU内部负责每个阶段的功能单元，
每个时钟周期要做的事情少了，提高时钟频率也变得简单了。

前面说过，有了流水线技术，理想情况下，每个时钟周期，CPU可以完成一条指令的执行。
那有没有什么方法，可以让CPU在每个时钟周期，完成多条指令的执行呢，这岂不是会大大提高CPU整体性能吗？

当然有！这就是Superscalar技术！（除此之外还有VLIW，)


/*
 * 超标量(Superscalar)
 */
    通过在CPU内部实现多条指令流水线，可以真正实现多条命令并行执行，也被称为多发射数据通路技术。
    以双发射流水线为例，每个时钟周期，CPU可以同时读取两条指令，然后同时对这两条指令进行译码，同时执行，然后同时写回。

    目前为止，这一切看起来都很完美，对吧？然而，现实往往没有理想那么丰满！

    流水线冲突

/*
 * 流水线冲突
 */
    现实中程序的指令序列之间往往存在各种各样的依赖和相关性，而CPU为了解决这种指令间的依赖和相关性，
    有时候不得不“停顿”下来，直到这些依赖得到解决，这就导致CPU指令流水线无法总是保持“全速运行”。

    这种现象被称之为Pipeline Hazard，

    归结起来，有三种情况：

    • 数据冲突(Data Hazard)

    • 控制冲突(Control Hazard)

    • 结构冲突(Structure Hazard)

/*
 * 数据冲突
 */
    所谓数据冲突，简单讲，就是两条在流水线中并行执行的指令，第二条指令需要用到第一条指令的执行结果，
    因此第二条指令的执行不得不暂停，一直到可以获取到第一条指令的执行结果为止。

    比如，用伪代码举例：
    x = 1;
    y = x;

    要对y进行赋值，必须要先得到x的值，因此这两条语句无法完全并行执行。

    这只是其中的一种典型情况，其他情况不再赘述。
/*
 * 控制冲突
 */

    所谓控制冲突，简单讲，就是在CPU在执行分支跳转时，无法预知下一条要执行的指令。

    比如：

    if(a > 100) {
        x = 1;
    } else {
        y = 2;
    }

    在CPU计算出a > 100这个条件是否成立之前，无法确定接下来是应该执行x = 1 还是执行 y = 2。

    为了解决这个问题，CPU可以简单的让流水线停顿一直到确定下一条要执行的指令，
    也可以采取如分支预测(branch prediction)和推测执行(speculation execution)等手段，
    但是，预测失败的话，流水线往往会受到比较严重的性能惩罚。

/*
 * 结构冲突
 */
    结构冲突，简单来说，就是多条指令同时竞争同一个硬件资源，由于硬件资源短缺，无法同时满足所有指令的执行请求。
    如两条并行执行的命令需要同时访问内存，而内存地址译码单元可能只有一个，这就产生了结构冲突。


/*
 * 优化热点路径内存访问
 */
    其实很简单，只需要把factorial02.c中定义局部变量的时候加上register关键字就可以了.
    C语言中，register关键字的作用是建议编译器，尽可能地把变量存放在寄存器中，以加快其访问速度。



#endif